<?php
/**
 * Author: DefinitlyEvil
 * Created at: 2020/5/13 13:35
 */

namespace App\Tools;


class TransactionTools
{
    const TYPES = ['gold', 'crown'];

    public static function getBalance(\PDO $pdo, $user_id, $type) {
        if(!is_int($user_id)) throw new \Exception('invalid user id type');
        if(!isset($type) or !in_array($type, self::TYPES)) throw new \Exception('invalid type');
        $stm = $pdo->prepare('SELECT COALESCE(SUM(`amount`), CAST(0 AS INT)) AS `balance` FROM `account_transactions` WHERE `account_id`=:id AND `type`=:t');
        $stm->bindParam(':id', $user_id, \PDO::PARAM_INT);
        $stm->bindParam(':t', $type, \PDO::PARAM_STR);
        if($stm->execute() === false) throw new \Exception('exec failed');
        $result = $stm->fetch(\PDO::FETCH_ASSOC);
        if($result === false) throw new \Exception('fetch failed');
        return intval($result['balance']);
    }

    const REDIS_LOCK_FORMAT = 'tx-lock:%d:%s'; // uid, type

    public static function transaction(\PDO $pdo, $user_id, $type, $amount, $reason = 'unknown') {
        if(!is_int($user_id) or !is_int($amount) or !is_string($reason)) throw new \Exception('invalid user id type');
        if(!isset($type) or !in_array($type, self::TYPES)) throw new \Exception('invalid type');
        if($amount === 0) throw new \Exception('can not do zero transactions');
        // lock in redis
        $redis = RedisHelper::connect();
        $redis_key = sprintf(self::REDIS_LOCK_FORMAT, $user_id, $type);
        if(!$redis->set($redis_key, sprintf('%d:%d:%s', time(), $user_id, $type), ['nx', 'ex' => 5])) {
            throw new \Exception('account is busy');
        }
        if($pdo->beginTransaction() === false) throw new \Exception('begin db transaction failed');
        try {
            $bal = self::getBalance($pdo, $user_id);
            if($bal + $amount < 0) return false;
            $stm = $pdo->prepare('INSERT INTO `account_transactions` (`account_id`,`amount`,`reason`) VALUES(:acc,:amount,:reason)');
            $stm->bindParam(':acc', $user_id, \PDO::PARAM_INT);
            $stm->bindParam(':amount', $amount, \PDO::PARAM_INT);
            $stm->bindParam(':reason', $reason, \PDO::PARAM_STR);
            if($stm->execute() === false) throw new \Exception('create transaction failed');
            if($pdo->commit() === false) throw new \Exception('commit failed');
            return true;
        } catch (\Exception $ex) {
            trigger_error('error: ' . $ex->getMessage());
            $pdo->rollBack();
            return false;
        } finally {
            $redis->delete($redis_key);
            $redis->close();
        }
    }
}